package me.chyxion.dao.datasource;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.List;
import org.apache.commons.lang3.ClassUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.core.annotation.Order;
import static me.chyxion.dao.datasource.DataSourceType.DEFAULT;

/**
 * Service Data Source Aspect
 * @version 0.0.2
 * @since 0.0.1
 * @author Shaun Chyxion <br />
 * chyxion@163.com <br />
 * Sep 6, 2014 9:26:44 PM
 */
@Aspect
@Order(0)
public class DataSourceAspect {
    private static final Logger log = LoggerFactory.getLogger(DataSourceAspect.class);
    
    @Pointcut("within(@org.springframework.stereotype.Service *)")
    public void serviceBean() {}

    @Pointcut("execution(public * *(..))")
    public void methodPointcut() {}

    @Before("serviceBean() && methodPointcut()")
    public void before(JoinPoint joinPoint) throws Throwable {
    	log.debug("Aspect Before.");
    	Object target = joinPoint.getTarget();
    	Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        log.debug("Before Method [{}#{}]", 
        		target.getClass().getSimpleName(),
        		method.getName());
        String dataSource = getDataSource(target, method);
        DataSourceHolder.set(dataSource);
        log.debug("Switch to dataSource: [{}].", dataSource);
    }

    @After("serviceBean() && methodPointcut()")
    public void afterReturn(JoinPoint joinPoint) throws Throwable {
        DataSourceHolder.remove();
        log.debug("Remove switched dataSource.");
    }

    private String getDataSource(Object target, Method method) {
    	DataSource dataSource = method.getAnnotation(DataSource.class);
        if (dataSource == null) {
        	log.debug("Method @DataSource Annotation Not Found, Try To Get From Class.");
            dataSource = target.getClass().getAnnotation(DataSource.class);
        }
        if (dataSource == null) {
        	// find super class annotation 
        	log.debug("Class Annotation @DataSource Not Found, Try To Get Super Class Annotation.");
        	dataSource = findDataSource(method, ClassUtils.getAllSuperclasses(target.getClass()));
        	if (dataSource == null) {
        		// find interface annotation 
        		log.debug("Class Annotation @DataSource Not Found, Try To Get Interface Annotation.");
        		dataSource = findDataSource(method, ClassUtils.getAllInterfaces(target.getClass()));
        	}
        }

        // no data source found, use default
        return dataSource == null ? DEFAULT : dataSource.value();
    }

    /**
     * find DataSource annotation 
     * @param method
     * @param classes
     * @return
     */
    private DataSource findDataSource(Method method, List<Class<?>> classes) {
    	DataSource dataSource = null;
    	log.debug("Class Annotation Data Source Not Found, Try To Find Parent Annotation To Get.");
    	for (Class<?> clazz : classes) {
    		Annotation annotation = null;
    		// method annotation 
    		Method m = BeanUtils.findMethod(clazz, method.getName(), method.getParameterTypes());
    		if (m != null) {
    			m.getAnnotation(DataSource.class);
    			if (annotation instanceof DataSource) {
    				dataSource = (DataSource) annotation;
    				break;
    			}
    		}
    		// class annotation 
    		annotation = clazz.getAnnotation(DataSource.class);
    		if (annotation instanceof DataSource) {
    			dataSource = (DataSource) annotation;
    			break;
    		}
    	}
    	return dataSource;
    }
}
